package club.jdiy.utils;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * JSON工具类
 *
 * @author ziquee(子秋) QQ号:39886616 主页: http://www.jdiy.club
 * @version 20200517
 */
@SuppressWarnings("unused")
public final class JsonUtils {
    private JsonUtils() {
    }

    /**
     * 将目标对象串化为json对象.
     *
     * @param obj 目标对象
     * @return 串化后的json字符串.
     */
    static public String stringify(Object obj) {
        try {
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

            JavaTimeModule timeModule = new JavaTimeModule();
            timeModule.addSerializer(LocalDateTime.class, new JsonSerializer<LocalDateTime>() {
                @Override
                public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
                    jsonGenerator.writeString(DateUtils.fmt_datetime.format(localDateTime));
                }
            });
            timeModule.addSerializer(LocalDate.class, new JsonSerializer<LocalDate>() {
                @Override
                public void serialize(LocalDate localDate, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
                    jsonGenerator.writeString(DateUtils.fmt_date.format(localDate));
                }
            });
            objectMapper.registerModule(timeModule);
            objectMapper.configure(SerializationFeature.INDENT_OUTPUT, Boolean.TRUE);
            objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
            return objectMapper.writeValueAsString(obj);
        } catch (Exception ex) {
            return obj.toString();
        }
    }

    /**
     * 将json字符串对象反串化为对象.
     *
     * @param s   json字符串
     * @param t   目标对象类型
     * @param <T> 目标对象的泛型
     * @return 返回反串化后的对象
     * @throws IOException 若转换过程中出现异常，则抛出.
     */
    public static <T> T parse(String s, Class<T> t) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        //配置在反序列化过程中如果json字符串中存在无法匹配的属性不会失败
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        //上面的configure(xxx,false) 等同于disable(xxx),例如下面这行和上面作用是一样的。
        //objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        JavaTimeModule timeModule = new JavaTimeModule();
        timeModule.addDeserializer(LocalDateTime.class, new JsonDeserializer<LocalDateTime>() {
            public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
                String s = jsonParser.getValueAsString();
                return LocalDateTime.parse(s, DateUtils.fmt_datetime);
            }
        });
        timeModule.addDeserializer(LocalDate.class, new JsonDeserializer<LocalDate>() {
            public LocalDate deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
                String s = jsonParser.getValueAsString();
                return LocalDate.parse(s, DateUtils.fmt_date);
            }
        });
        objectMapper.registerModule(timeModule);
        return objectMapper.readValue(s, t);
    }

    /**
     * 转换复杂一点的类型，如数组，List等类型．
     *
     * @param s   　json 字符串
     * @param t   类型描述，如：　JsonUtils.parse(jsonData, new TypeReference<List<HejiaVo>>() {})
     * @param <T> 返回的类型．对应TypeReference里面的东西.
     * @return TypeReference里面的类型
     * @throws IOException ex
     */
    public static <T> T parse(String s, TypeReference<T> t) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        //配置在反序列化过程中如果json字符串中存在无法匹配的属性不会失败
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        //上面的configure(xxx,false) 等同于disable(xxx),例如下面这行和上面作用是一样的。
        //objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        JavaTimeModule timeModule = new JavaTimeModule();
        timeModule.addDeserializer(LocalDateTime.class, new JsonDeserializer<LocalDateTime>() {
            public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
                String s = jsonParser.getValueAsString();
                return LocalDateTime.parse(s, DateUtils.fmt_datetime);
            }
        });
        timeModule.addDeserializer(LocalDate.class, new JsonDeserializer<LocalDate>() {
            public LocalDate deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
                String s = jsonParser.getValueAsString();
                return LocalDate.parse(s, DateUtils.fmt_date);
            }
        });
        objectMapper.registerModule(timeModule);
        return objectMapper.readValue(s, t);
    }


    /**
     * 此方法可以将一个json转换成可阅读的方式显示(添加转行，综进等排版，让json字符串方便阅读)
     *
     * @param jsonStr 源json字符串
     * @return 格式化后的可阅读json.
     */
    public static String format(String jsonStr) {
        if (StringUtils.isEmpty(jsonStr)) return "";
        try (ByteArrayInputStream in = new ByteArrayInputStream(jsonStr.getBytes());
             ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            char ch;
            int read;
            int space = 0;
            while ((read = in.read()) > 0) {
                ch = (char) read;
                switch (ch) {
                    case '{': {
                        space = outputAndRightMove(space, ch, out);
                        break;
                    }
                    case '[': {
                        out.write(ch);
                        space += 2;
                        break;
                    }
                    case '}':
                    case ']': {
                        space = outputAndLeftMove(space, ch, out);
                        break;
                    }
                    case ',': {
                        out.write(ch);
                        outputNewline(out);
                        out.write(getBlankingStringBytes(space));
                        break;
                    }
                    default: {
                        out.write(ch);
                        break;
                    }
                }
            }
            return out.toString();
        } catch (IOException e) {
            //e.printStackTrace();
        }
        return jsonStr;
    }

    private static int outputAndRightMove(int space, char ch, ByteArrayOutputStream out) throws IOException { //换行
        outputNewline(out); //向右缩进
        out.write(getBlankingStringBytes(space));
        out.write(ch);
        outputNewline(out);
        space += 2; //再向右缩进多两个字符
        out.write(getBlankingStringBytes(space));
        return space;
    }


    private static int outputAndLeftMove(int space, char ch, ByteArrayOutputStream out) throws IOException {
        outputNewline(out);
        space -= 2;
        out.write(getBlankingStringBytes(space));
        out.write(ch);
        return space;
    }

    private static byte[] getBlankingStringBytes(int space) {
        String sb = IntStream.range(0, space).mapToObj(i -> " ").collect(Collectors.joining());
        return sb.getBytes();
    }

    private static void outputNewline(ByteArrayOutputStream out) {
        out.write('\n');
    }
}

